package com.nvwa.jasper;

import com.nvwa.coding.util.json.JsonUtil;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import net.sf.jasperreports.engine.*;
import net.sf.jasperreports.engine.data.JRBeanCollectionDataSource;
import net.sf.jasperreports.engine.export.HtmlExporter;
import net.sf.jasperreports.export.HtmlExporterOutput;
import net.sf.jasperreports.export.SimpleExporterInput;
import net.sf.jasperreports.export.SimpleHtmlExporterOutput;
import net.sf.jasperreports.export.SimpleHtmlReportConfiguration;
import org.springframework.core.io.ClassPathResource;

import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.Map;

@Slf4j
public class JasperReportUtil {
    final static String jasperDir = "reports/";

    /**
     * 通过jasper模板导出pdf
     *
     * @param jasperTemplateName jasper模板名称，不带.jasper后缀（模板放在resource目录下的reports文件夹下）
     * @param newFileName        导出文件名，不带.pdf后缀
     * @param parameters         模板中$P参数填充的参数
     * @param dataSource         列表填充数据源，没有列表传new JREmptyDataSource()，列表有数据传new JRBeanCollectionDataSource(list)
     * @param response           响应对象
     */
    @SneakyThrows
    public static void exportToPdf(String jasperTemplateName, String newFileName, Map<String, Object> parameters, JRDataSource dataSource, HttpServletResponse response) {
        log.info("---M[exportToPdf] export pdf start, jasperTemplateName: {}, newFileName: {}, parameters: {}, dataSource: {}"
                , jasperTemplateName, newFileName, JsonUtil.toString(parameters), dataSource);

        //参数templateName检查
        if (jasperTemplateName == null || jasperTemplateName.trim().isEmpty()) {
            throw new RuntimeException("---M[exportToPdf] param templateName can not be null!");
        }

        if (dataSource == null) {
            throw new RuntimeException("---M[exportToPdf] param dataSource can not be null!");
        }

        if (parameters != null) {
            parameters.put("REPORT_DIR", jasperDir);
        } else {
            parameters = Collections.emptyMap();
            parameters.put("REPORT_DIR", jasperDir);
        }

        //设置响应头
        newFileName = (newFileName == null || newFileName.trim().isEmpty()) ? "pdf_report" : newFileName;
        OutputStream outputStream = setResponse(newFileName, response);

        //写到response中
        String tempPath = getJasperFileDir(jasperTemplateName);
        ClassPathResource resource = new ClassPathResource(tempPath);
        try (InputStream jasperStream = resource.getInputStream()) {
            JasperPrint jasperPrint = JasperFillManager.fillReport(jasperStream, parameters, dataSource);
            JasperExportManager.exportReportToPdfStream(jasperPrint, outputStream);
            log.info("---M[exportToPdf] jasper export pdf done!");
        } catch (Exception e) {
            log.error("---M[exportToPdf] jasper export pdf exception!", e);
            outputStream.write("jasper export pdf exception!".getBytes());
        }
    }

    private static OutputStream setResponse(String newFileName, HttpServletResponse response) throws IOException {
        String charset = StandardCharsets.UTF_8.name();
        response.setCharacterEncoding(charset);
        String newFilename = URLEncoder.encode(newFileName, StandardCharsets.UTF_8.name());
        response.setHeader("Content-Disposition", String.format("attachment;filename*=%s''%s.pdf", charset.toLowerCase(), newFilename));
        response.setContentType(getContentType(ReportType.PDF));
        OutputStream outputStream = response.getOutputStream();
        log.info("---M[setResponse] set response success, newFileName: {}", newFileName);
        return outputStream;
    }


    /**
     * 设置响应头
     *
     * @param newFileName 文件名
     * @param response    响应体
     */
    private static void setRespHeader(String newFileName, HttpServletResponse response) {
        log.info("---M[setRespHeader] jasper export file set response header new file name: {}", newFileName);
        String charset = StandardCharsets.UTF_8.name();
        response.setCharacterEncoding(charset);
        String newFilename = null;
        try {
            newFilename = URLEncoder.encode(newFileName, StandardCharsets.UTF_8.name());
        } catch (UnsupportedEncodingException e) {
            log.error("---M[setRespHeader] jasper export file set response header failed.", e);
            throw new RuntimeException(e);
        }
        response.setHeader("Content-Disposition", String.format("attachment;filename*=%s''%s.pdf", charset.toLowerCase(), newFilename));
        response.setContentType(getContentType(ReportType.PDF));
    }


    private static void exportToXml(String jasperPath, Map<String, Object> sourceMap, Map<String, Object> paramMap, HttpServletResponse response) throws Exception {
        OutputStream outputStream = response.getOutputStream();
        try {
            ClassPathResource resource = new ClassPathResource(jasperPath);
            response.setContentType(getContentType(ReportType.XML));
            InputStream jasperStream = resource.getInputStream();
            JasperPrint jasperPrint = getJasperPrint(jasperStream, sourceMap, paramMap);
            JasperExportManager.exportReportToXmlStream(jasperPrint, outputStream);
        } catch (Exception e) {
            log.error("---M[exportToXml]  jasper export xml exception!");
            outputStream.write("jasper export xml exception!".getBytes());
        } finally {
            outputStream.flush();
            outputStream.close();
        }
    }

    private static void exportToHtml(String jasperPath, Map<String, Object> sourceMap, Map<String, Object> paramMap, HttpServletResponse response) throws Exception {
        response.setHeader("Content-type", "text/html;charset=utf-8");
        response.setCharacterEncoding("utf-8");
        response.setContentType(getContentType(ReportType.HTML));
        PrintWriter out = response.getWriter();
        HtmlExporter exporter = new HtmlExporter();
        try {
            ClassPathResource resource = new ClassPathResource(jasperPath);
            InputStream jasperStream = resource.getInputStream();
            JasperPrint jasperPrint = getJasperPrint(jasperStream, sourceMap, paramMap);

            exporter.setExporterInput(new SimpleExporterInput(jasperPrint));

            SimpleHtmlReportConfiguration configuration = new SimpleHtmlReportConfiguration();
            exporter.setConfiguration(configuration);

            HtmlExporterOutput outPut = new SimpleHtmlExporterOutput(out);
            exporter.setExporterOutput(outPut);

            exporter.exportReport();
        } catch (Exception e) {
            log.error("---M[exportToXml]  jasper export html exception!");
            out.write("jasper export html exception!");
        } finally {
            out.flush();
            out.close();
        }
    }

    private static String getJasperFileDir(String fileName) {
        return jasperDir + fileName + ".jasper";
    }

    private static JasperPrint getJasperPrint(InputStream jasperStream, Map<String, Object> sourceMap, Map<String, Object> paramMap) throws JRException {
        JRDataSource dataSource;
        if (null == paramMap || paramMap.isEmpty()) {
            dataSource = new JREmptyDataSource();
        } else {
            dataSource = new JRBeanCollectionDataSource(Collections.singletonList(paramMap));
        }
        JasperPrint jasperPrint = JasperFillManager.fillReport(jasperStream, sourceMap, dataSource);
        return jasperPrint;
    }

    private static String getContentType(ReportType type) {
        String contentType;
        switch (type) {
            case HTML:
                contentType = "text/html;charset=utf-8";
                break;
            case PDF:
                contentType = "application/pdf";
                break;
            case XLS:
                contentType = "application/vnd.ms-excel";
                break;
            case XLSX:
                contentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
                break;
            case XML:
                contentType = "text/xml";
                break;
            case RTF:
                contentType = "application/rtf";
                break;
            case CSV:
                contentType = "text/plain";
                break;
            case DOC:
                contentType = "application/msword";
                break;
            default:
                contentType = "text/html;charset=utf-8";
        }
        return contentType;
    }

    private enum ReportType {
        HTML, PDF, XLS, XLSX, XML, RTF, CSV, DOC
    }
}